Skip to main content

nexus\api\event/
mod.rs

1//! Event system.
2//!
3//! ```no_run
4//! use nexus::{
5//!     event::{ADDON_LOADED, event_consume},
6//!     log::{log, LogLevel}
7//! };
8//! use std::ptr::NonNull;
9//!
10//! let callback = event_consume!(|payload: Option<&i32>| {
11//!     if let Some(signature) = payload {
12//!         log(LogLevel::Info, "My Addon", format!("Addon {signature} loaded"));
13//!     }
14//! });
15//!
16//! ADDON_LOADED.subscribe(callback);
17//! ```
18
19mod nexus;
20
21#[cfg(feature = "arc")]
22pub mod arc;
23
24#[cfg(feature = "extras")]
25pub mod extras;
26
27#[cfg(feature = "rtapi")]
28pub mod rtapi;
29
30use super::EventApi;
31use crate::{AddonApi, ffi::str_to_c, revertible::Revertible};
32use std::{
33    ffi::{c_char, c_void},
34    marker::PhantomData,
35    mem,
36};
37
38pub use self::nexus::*;
39
40/// An event identifier & payload type pair.
41#[derive(Debug, Clone, Copy)]
42pub struct Event<T> {
43    pub identifier: &'static str,
44    _phantom: PhantomData<T>,
45}
46
47impl<T> Event<T> {
48    /// Creates a new event identifier & payload type pair.
49    ///
50    /// # Safety
51    /// See [`event_subscribe_typed`].
52    #[inline]
53    pub const unsafe fn new(identifier: &'static str) -> Self {
54        Self {
55            identifier,
56            _phantom: PhantomData,
57        }
58    }
59
60    /// Subscribes to the event.
61    #[inline]
62    pub fn subscribe(
63        &self,
64        callback: RawEventConsume<T>,
65    ) -> Revertible<impl Fn() + Send + Sync + Clone + 'static> {
66        unsafe { event_subscribe_typed(self.identifier, callback) }
67    }
68
69    /// Unsubscribes a previously registered event callback.
70    #[inline]
71    pub fn unsubscribe(&self, callback: RawEventConsume<T>) {
72        let callback =
73            unsafe { mem::transmute::<RawEventConsume<T>, RawEventConsumeUnknown>(callback) };
74        event_unsubscribe(self.identifier, callback)
75    }
76
77    /// Raises the event.
78    #[inline]
79    pub fn raise(&self, event_data: &T) {
80        unsafe { event_raise(self.identifier, event_data) }
81    }
82}
83
84pub type RawEventConsume<T> = extern "C-unwind" fn(event_args: *const T);
85
86pub type RawEventConsumeUnknown = RawEventConsume<c_void>;
87
88pub type RawEventRaise =
89    unsafe extern "C-unwind" fn(identifier: *const c_char, event_data: *const c_void);
90
91pub type RawEventRaiseNotification = unsafe extern "C-unwind" fn(identifier: *const c_char);
92
93pub type RawEventRaiseTargeted = unsafe extern "C-unwind" fn(
94    signature: i32,
95    identifier: *const c_char,
96    event_data: *const c_void,
97);
98
99pub type RawEventRaiseNotificationTargeted =
100    unsafe extern "C-unwind" fn(signature: i32, identifier: *const c_char);
101
102pub type RawEventSubscribe = unsafe extern "C-unwind" fn(
103    identifier: *const c_char,
104    consume_callback: RawEventConsumeUnknown,
105);
106
107/// Subscribes to an event with a raw callback using an unknown payload.
108///
109/// Returns a [`Revertible`] to revert the subscribe.
110pub fn event_subscribe_unknown(
111    identifier: impl AsRef<str>,
112    callback: RawEventConsumeUnknown,
113) -> Revertible<impl Fn() + Send + Sync + Clone + 'static> {
114    let identifier = str_to_c(identifier).expect("failed to convert event identifier");
115    let EventApi {
116        subscribe,
117        unsubscribe,
118        ..
119    } = AddonApi::get().event;
120    unsafe { subscribe(identifier.as_ptr(), callback) };
121    let revert = move || unsafe { unsubscribe(identifier.as_ptr(), callback) };
122    revert.into()
123}
124
125/// Subscribes to an event with a raw callback using a typed payload.
126///
127/// Returns a [`Revertible`] to revert the subscribe.
128///
129/// # Safety
130/// The passed event identifier must always come with valid data of the given type.
131pub unsafe fn event_subscribe_typed<T>(
132    identifier: impl AsRef<str>,
133    callback: RawEventConsume<T>,
134) -> Revertible<impl Fn() + Send + Sync + Clone + 'static> {
135    let callback =
136        unsafe { mem::transmute::<RawEventConsume<T>, RawEventConsumeUnknown>(callback) };
137    event_subscribe_unknown(identifier, callback)
138}
139
140/// Unsubscribes a previously registered raw event callback.
141pub fn event_unsubscribe(identifier: impl AsRef<str>, callback: RawEventConsumeUnknown) {
142    let identifier = str_to_c(identifier).expect("failed to convert event identifier");
143    let EventApi { unsubscribe, .. } = AddonApi::get().event;
144    unsafe { unsubscribe(identifier.as_ptr(), callback) }
145}
146
147/// Macro to wrap an event callback.
148///
149/// Generates a [`RawEventConsume`] wrapper around the passed callback.
150///
151/// # Usage
152/// ```no_run
153/// # use nexus::event::*;
154/// let event_callback = event_consume!(|data: Option<&i32>| {
155///     use nexus::log::{log, LogLevel};
156///     log(LogLevel::Info, "My Addon", format!("received event with data {data:?}"));
157/// });
158///
159/// let event_callback = event_consume!(<i32> |data| {
160///     use nexus::log::{log, LogLevel};
161///     log(LogLevel::Info, "My Addon", format!("received event with data {data:?}"));
162/// });
163/// ```
164///
165/// ```no_run
166/// # use nexus::event::*;
167/// fn event_callback(data: Option<&i32>) {
168///     use nexus::log::{log, LogLevel};
169///     log(LogLevel::Info, "My Addon", format!("Received event with data {data:?}"));
170/// }
171/// let event_callback = event_consume!(<i32> event_callback);
172/// ```
173///
174/// Note that the payload type corresponds to the pointee in Nexus documentation.
175/// If you are interested in the pointer itself, you have to cast the obtained reference back to a pointer:
176/// ```no_run
177/// # use nexus::event::*;
178/// use std::ffi::{c_char, CStr};
179///
180/// let event_callback = event_consume!(<c_char> |data| {
181///     if let Some(data) = data {
182///         let ptr = data as *const c_char;
183///         let c_str = unsafe { CStr::from_ptr(ptr) };
184///     }
185/// });
186/// ```
187#[macro_export]
188macro_rules! event_consume {
189    ( < $ty:ty > $callback:expr $(,)? ) => {{
190        const __CALLBACK: fn(::std::option::Option<&$ty>) = ($callback);
191
192        extern "C-unwind" fn __event_callback_wrapper(data: *const $ty) {
193            let _ = unsafe { ::std::mem::transmute::<*const $ty, *const ::std::ffi::c_void>(data) }; // size check
194            __CALLBACK(unsafe { data.as_ref() })
195        }
196
197        __event_callback_wrapper
198    }};
199    ( $ty:ty , $callback:expr $(,)? ) => {
200        $crate::event::event_consume!(<$ty> $callback)
201    };
202    ( | $arg:ident : Option<& $ty:ty > | $body:expr $(,)? ) => {
203        $crate::event::event_consume!(<$ty> |$arg: Option<& $ty >| $body)
204    };
205    ( $callback:expr $(,)? ) => {{
206        $crate::event::event_consume!(<()> $callback)
207    }};
208}
209
210pub use event_consume;
211
212/// Macro to subscribe to an event with a wrapped callback.
213///
214/// This macro is [unsafe](https://doc.rust-lang.org/std/keyword.unsafe.html).
215/// See [`event_subscribe_typed`] for more information.
216///
217/// Returns a [`Revertible`] to revert the subscribe.
218///
219/// # Usage
220/// ```no_run
221/// # use nexus::event::*;
222/// unsafe {
223///     event_subscribe!("MY_EVENT" => i32, |data| {
224///         use nexus::log::{log, LogLevel};
225///         log(LogLevel::Info, "My Addon", format!("Received MY_EVENT with {data:?}"));
226///     })
227/// }.revert_on_unload();
228/// ```
229///
230/// The event identifier may be dynamic and the callback can be a function name.
231/// ```no_run
232/// # use nexus::event::*;
233/// let event: &str = "MY_EVENT";
234/// fn event_callback(data: Option<&i32>) {
235///     use nexus::log::{log, LogLevel};
236///     log(LogLevel::Info, "My Addon", format!("Received MY_EVENT with {data:?}"));
237/// }
238/// let revertible = unsafe { event_subscribe!(event => i32, event_callback) };
239/// revertible.revert();
240/// ```
241///
242/// The `unsafe` keyword can be moved into the macro call:
243/// ```no_run
244/// # use nexus::event::*;
245/// # fn event_callback(_: Option<&()>) {}
246/// event_subscribe!(unsafe "MY_EVENT" => (), event_callback);
247/// ```
248/// Note that the payload type corresponds to the pointee in Nexus documentation.
249/// If you are interested in the pointer itself, you have to cast the obtained reference back to a pointer:
250/// ```no_run
251/// # use nexus::event::*;
252/// use std::ffi::{c_char, CStr};
253///
254/// event_subscribe!(unsafe "EV_ACCOUNT_NAME" => c_char, |data| {
255///     if let Some(data) = data {
256///         let ptr = data as *const c_char;
257///         let c_str = unsafe { CStr::from_ptr(ptr) };
258///     }
259/// });
260/// ```
261///
262/// # Safety
263/// See [`event_subscribe_typed`].
264#[macro_export]
265macro_rules! event_subscribe {
266    ( unsafe $event:expr , $ty:ty , $callback:expr $(,)? ) => {
267        unsafe { $crate::event::event_subscribe!($event => $ty, $callback) }
268    };
269    ( unsafe $event:expr => $ty:ty , $callback:expr $(,)? ) => {
270        unsafe { $crate::event::event_subscribe!($event => $ty, $callback) }
271    };
272    ( $event:expr , $ty:ty , $callback:expr $(,)? ) => {
273        $crate::event::event_subscribe!($event => $ty, $callback)
274    };
275    ( $event:expr => $ty:ty , $callback:expr $(,)? ) => {
276        $crate::event::event_subscribe_typed($event, $crate::event::event_consume!(<$ty> $callback))
277    };
278}
279
280pub use event_subscribe;
281
282/// Raises an event to all subscribing addons.
283///
284/// # Safety
285/// The passed event identifier must be associated with data of the given type.
286pub unsafe fn event_raise<T>(identifier: impl AsRef<str>, event_data: &T) {
287    let identifier = str_to_c(identifier).expect("failed to convert event identifier");
288    let data: *const _ = event_data;
289    let EventApi { raise, .. } = AddonApi::get().event;
290    unsafe { raise(identifier.as_ptr(), data.cast()) }
291}
292
293/// Raises an event without payload to all subscribing addons.
294pub fn event_raise_notification(identifier: impl AsRef<str>) {
295    let identifier = str_to_c(identifier).expect("failed to convert event identifier");
296    let EventApi {
297        raise_notification, ..
298    } = AddonApi::get().event;
299    unsafe { raise_notification(identifier.as_ptr()) }
300}
301
302/// Raises an event for a specific subscribing addon.
303///
304/// # Safety
305/// See [`event_raise`].
306pub unsafe fn event_raise_targeted<T>(signature: i32, identifier: impl AsRef<str>, event_data: &T) {
307    let identifier = str_to_c(identifier).expect("failed to convert event identifier");
308    let data: *const _ = event_data;
309    let EventApi { raise_targeted, .. } = AddonApi::get().event;
310    unsafe { raise_targeted(signature, identifier.as_ptr(), data.cast()) }
311}
312
313/// Raises an event without payload for a specific subscribing addon.
314pub fn event_raise_notification_targeted(signature: i32, identifier: impl AsRef<str>) {
315    let identifier = str_to_c(identifier).expect("failed to convert event identifier");
316    let EventApi {
317        raise_notification_targeted,
318        ..
319    } = AddonApi::get().event;
320    unsafe { raise_notification_targeted(signature, identifier.as_ptr()) }
321}